package code.google.jcontainer.invoke;

import code.google.jcontainer.annotation.Resolve;
import code.google.jcontainer.resolver.MethodResolveContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * InvocationHandler for resolving Annotation.
 *
 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */
public class AnnotationResolverInvocationHandler implements InvocationHandler {

    /**
     * will resolve class annotations and method annotations that when=Resolve.WHEN.BEFORE_METHOD
     * @param invocation
     * @throws Exception
     */
    public void chainInvoke(Invocation invocation) throws Exception {
        Object component = invocation.getTargetComponent();
        Method method = invocation.getTargetMethod();
        Object[] parameters = invocation.getParameters();

        MethodResolveContext methodResolveContext = null;        
        Annotation[] classAnnotations = invocation.getComponentMeta().getClassAnnotations();
        if (classAnnotations.length > 0 || invocation.getMethodAnnotations().length > 0) {
            methodResolveContext = new MethodResolveContext(invocation.getContainer(), invocation.getComponentMeta().getName(), invocation.getComponentClass(), component, method, parameters, null);
            invocation.setAnnotationResolveContext(methodResolveContext);
        }
        //resolve class annotations, such as @Inceptor on the class
        if (classAnnotations.length > 0) {
            for (Annotation classAnnotation : classAnnotations) {
                methodResolveContext.setAnnotation(classAnnotation);
                invocation.getAnnotationResolveRepository().resolveClassAnnotationAroundMethod(methodResolveContext, Resolve.WHEN.BEFORE_METHOD);
            }
        }
        if (invocation.getMethodAnnotations().length > 0) {
            //resolve method annotation before invoke method, such as @Log            
            for (Annotation methodAnnotation : invocation.getMethodAnnotations()) {
                methodResolveContext.setAnnotation(methodAnnotation);
                invocation.getAnnotationResolveRepository().resolveMethodAnnotationAroundMethod(methodResolveContext, Resolve.WHEN.BEFORE_METHOD);
            }
            // set invocation's parameters according to methodContext's parameters, may set by InterceptContext
            invocation.setParameters(methodResolveContext.getParameters());
        }
    }

    /**
     * will resolve class annotations and method annotations that when=Resolve.WHEN.AFTER_METHOD
     * @param invocation
     * @throws Exception
     */
    public void chainReturn(Invocation invocation) throws Exception {
        MethodResolveContext methodResolveContext = invocation.getAnnotationResolveContext();
        //resolve class annotations, such as @Inceptor on the class
        Annotation[] classAnnotations = invocation.getComponentMeta().getClassAnnotations();
        if (classAnnotations.length > 0) {
            for (Annotation classAnnotation : classAnnotations) {
                methodResolveContext.setAnnotation(classAnnotation);
                invocation.getAnnotationResolveRepository().resolveClassAnnotationAroundMethod(methodResolveContext, Resolve.WHEN.AFTER_METHOD);
            }
        }
        if (invocation.getMethodAnnotations().length > 0) {
            // method annotations
            methodResolveContext.setResult(invocation.getResult());
            for (Annotation methodAnnotation : invocation.getMethodAnnotations()) {
                methodResolveContext.setAnnotation(methodAnnotation); // need to set annotation again
                invocation.getAnnotationResolveRepository().resolveMethodAnnotationAroundMethod(methodResolveContext, Resolve.WHEN.AFTER_METHOD);
            }
            invocation.setResult(methodResolveContext.getResult());
        }

    }

    public void onCaughtException(Invocation invocation, Exception e) {
        invocation.onCaughtException(e);
    }
}